feat: multi-app kernel circuits#23076
Conversation
…accept it as a previous kernel Introduces VK tree index 65 for the batched init_3 kernel and adds it to the ALLOWED_PREVIOUS_CIRCUITS lists for inner, reset, tail, and tail-to-public so an init_3 kernel may precede them. Index 65 is appended after the 23..64 reset variants; comment in constants.nr updated to reserve indices 24..64 for reset and reopen 65+ for new non-reset kernel circuits. Regenerated TS-side constants via `yarn remake-constants`.
Most downstream scripts (e.g. noir-protocol-circuits/bootstrap.sh) call find-bb, which returns bb-avm by default. Rebuilding only the non-AVM `bb` target therefore leaves a stale bb-avm and downstream bootstraps keep failing with the same error after the supposed fix.
…nterface methods Concrete inputs class for the batched init_3 kernel (three PrivateCallData fields), exported from @aztec/stdlib/kernel. PrivateKernelProver gains generateInit3Output / simulateInit3 mirroring the existing init pair. Generic over N is deferred until a second variant lands.
…ct and VK Adds PrivateKernelInit3Artifact to the ClientProtocolArtifact union and to the artifact / VK / VK-index registries. The simulated lookup reuses the constrained init_3 artifact, since no private-kernel-init-3-simulated crate exists. The dynamic-import generator gains a small additive pass that emits fallback cases mapping <name>_simulated to the constrained <name>.json for any artifact in artifactsWithoutSimulatedVersions, so lazy and bundle providers stay in sync (the bundle provider has reused the constrained artifact for hiding kernels' simulated slot all along). Adds 'private-kernel-init-3' to ClientCircuitName for stats plumbing. Adds private_kernel_init_3 to the codegen list so the Noir ABI type flows through. Generated files (client_artifacts_helper.ts, types/index.ts, vk_tree.ts) are gitignored and produced by `yarn generate`.
Adds witness-map converters for the batched private_kernel_init_3 circuit and wires them into BBPrivateKernelProver. Mirrors the existing init pair; the input mapper matches the Noir ABI for private_kernel_init_3/main.nr (three private_call_N + three app_public_inputs_N fields).
When PXE_USE_INIT_3=1 is set and the tx has at least three private app calls, the first kernel iteration verifies three apps in a single batched private_kernel_init_3 circuit instead of one private_kernel_init followed by inners. Saves N-1 prev-kernel HN verifications across the chain. Falls back to the standard init for txs with fewer than three calls so the flag can be left on permanently. Refactors the loop's pop-stack / push-nested / record-step / build-call-data sequence into a consumeNextApp helper used at every app-consumption site (top of loop + the two follow-up apps in the init_3 branch). Total app count is computed once via the existing collectNested helper. Leaves a WORKTODO(luke) for the case where init_3 consumed every app in the tx and the chain can skip directly to reset+tail without an inner kernel chain.
scripts/reproduce_init3_canary.sh runs a real client_flow with PXE_USE_INIT_3=1, captures the IVC inputs, proves them with bb (Chonk), and verifies the proof. Asserts the capture references PrivateKernelInit3Artifact so a regression in the orchestrator's flag plumbing is caught at capture time. Gates the captureProfile expectedSteps check on SKIP_STEP_COUNT_CHECK=1 so the test doesn't abort before writing the msgpack when kernel batching collapses steps.
e6d8db4 to
755b955
Compare
The Private-Kernel-Sequencer suite asserts call counts on proofCreator.simulateInit / .simulateInner with the single-app dispatch shape (e.g. expect(simulateInit).toHaveBeenCalledTimes(1) and expect(simulateInner).toHaveBeenCalledTimes(3) for nested calls). With the new default PXE_KERNEL_BATCH_SIZE=3 the planner routes through simulateInit2 / .simulateInit3 / .simulateInner2 / .simulateInner3, none of which have mock return values configured here, so the orchestrator crashes reading bytecode off undefined. Multi-app dispatch is integration-tested via the bench captures; this unit suite is about the single-app orchestration path, so pin the env var locally and restore it on teardown.
Multi-app kernel batching added 4 new stdlib classes
(PrivateKernelInit{2,3}CircuitPrivateInputs,
PrivateKernelInner{2,3}CircuitPrivateInputs), 4 corresponding
witness-map converters in noir-protocol-circuits-types, and the
BatchPlanner orchestrator helper. Main entrypoint grew from ~1733 KB
to 1760 KB, exceeding the 1750 KB hard limit by 10.72 KB.
Bump to 1800 KB to clear the limit and leave ~40 KB headroom for the
inevitable init_4 / inner_4 work.
The test asserts that a `PrivateKernelInit_main` recording exists and that its content has `circuitName: 'PrivateKernelInit'`. With the new default batch size of 3 the orchestrator routes the first kernel through `private_kernel_init_3` instead, producing a `PrivateKernelInit3_main` recording — so the lookup returns undefined. The test is about the recorder mechanism, not kernel batching, so pin the batch size to 1 for the duration of the test and restore on teardown.
| // kernel). With the default batch size of 3 the orchestrator picks `private_kernel_init_3` | ||
| // instead. This test is about the recorder mechanism, not kernel batching, so pin the batch | ||
| // size to 1 for the duration of the test. | ||
| const originalBatchSize = process.env.PXE_KERNEL_BATCH_SIZE; |
There was a problem hiding this comment.
I think it would be fine to run this test with the default batching and redo the assertions, they're not that exhaustive anyways
| // Multi-app behaviour is integration-tested via the bench capture flows. | ||
| const originalBatchSize = process.env.PXE_KERNEL_BATCH_SIZE; | ||
| beforeAll(async () => { | ||
| process.env.PXE_KERNEL_BATCH_SIZE = '1'; |
There was a problem hiding this comment.
We should probably test multiple batch sizes here.
|
Just amazing, I'm so eager to try this! A nit though: I'd prefer if PXE_KERNEL_BATCH_SIZE was wired in PXE's config rather than being a plain env var. In browser environments process.env has to be mocked and is a lot better to configure the wallet/pxe explicitly at init time |
…de/integrate-init-3
Cherry-pick of #23092 onto merge-train/fairies. Same flake (`verifies transactions at 10 TPS` in `tx_stats_bench.test.ts:268`) is now blocking PRs based on this train; see #23092 for the full analysis (bb.js NativeUnixSocket backend under 8x parallel IVC verifications intermittently returning `valid:false`). Skipping at sub-test granularity keeps the other three serial sub-tests emitting their compression and single-tx verification metrics; only the IVC-verifier-under-concurrency metrics are dropped. The `.test_patterns.yml` flake-retry entry from #23083 stays in place.
| pub global PARITY_ROOT_VK_INDEX: u32 = 22; | ||
| pub global PRIVATE_KERNEL_RESET_VK_INDEX: u32 = 23; | ||
| // Important: Do not define indexes after the PRIVATE_KERNEL_RESET_VK_INDEX. They are allocated for the variants of private kernel reset. | ||
| // Important: Do not define indexes between 24 and 64. They are allocated for the variants of private kernel reset. Add new non-reset kernel indices at 65+. |
There was a problem hiding this comment.
I should've done this before. In generate_vk_tree.ts, can we add some checks:
const vkHashes = new Array(2 ** VK_TREE_HEIGHT).fill(Buffer.alloc(32));
for (const [key, value] of Object.entries(allVks)) {
const index = ProtocolCircuitVkIndexes[key as ProtocolArtifact];
if (index >= vkHashes.length) { // <- new
throw new Error(`VK index ${index} is out of bounds. VK tree size: ${vkHashes.length}`);
}
if (!vkHashes[index].equals(Buffer.alloc(32))) { // <- new
throw new Error(`VK hash for ${key} already exists at index ${index}`);
}
vkHashes[index] = value.keyAsFields.hash.toBuffer();
}
Just in case we have more reset circuits one day and the last one's index is larger than 64.
federicobarbacovi
left a comment
There was a problem hiding this comment.
Reviewed via active collaboration
Integrates init/inner kernels that process up to 3 apps into the PXE